package se.kayarr.cardplay.shared;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import lombok.Getter;
import se.kayarr.cardplay.exceptions.PlayerUnspecifiedException;
import se.kayarr.cardplay.exceptions.PositionOutsideFieldException;
import se.kayarr.cardplay.exceptions.TooManyPlayersException;
import se.kayarr.cardplay.gameplay.Card;
import se.kayarr.cardplay.gameplay.Field;
import se.kayarr.cardplay.gameplay.Player;
import se.kayarr.cardplay.gameplay.Session;
import se.kayarr.cardplay.gameplay.UnitCard;

public class StringReferenceParser {
	private StringReferenceParser() {
	}
	
	private static Pattern refInListRegex = Pattern.compile("(\\d+)|([\\?UA]\\p{XDigit}{3})", Pattern.CASE_INSENSITIVE);
	
	private static Pattern refOnFieldRegex = Pattern.compile("(\\d+),(\\d+)|([\\?UA]\\p{XDigit}{3})",
			Pattern.CASE_INSENSITIVE);
	
	private static Pattern instanceRefRegex = Pattern.compile("^(?:(.+)\\.|(@))?(\\w+):(.+)$", Pattern.CASE_INSENSITIVE);
	
	public static Card.Instance getInstanceFrom(String cardReferenceString, Iterable<? extends Card.Instance> cards) {
		Matcher matcher = refInListRegex.matcher(cardReferenceString);
		
		if(matcher.find()) {
			if(matcher.group(1) != null) {
				int position = Integer.parseInt(matcher.group(1)) - 1;
				
				try {
					return Util.getItemAtPos(cards, position);
				}
				catch(IndexOutOfBoundsException e) {
					return null;
				}
			}
			else if(matcher.group(2) != null) {
				for(Card.Instance instance : cards) {
					if(instance.getCard().getDisplayId().equals(matcher.group(2))) return instance;
				}
			}
		}
		return null;
	}
	
	public static UnitCard.Instance getInstanceFrom(String cardReferenceString, Field field)
			throws PositionOutsideFieldException {
		Matcher matcher = refOnFieldRegex.matcher(cardReferenceString);
		
		if(matcher.find()) {
			if(matcher.group(1) != null && matcher.group(2) != null) {
				int x = Integer.parseInt(matcher.group(1)) - 1;
				int y = Integer.parseInt(matcher.group(2)) - 1;
				
				try {
					return field.getUnitCard(x, y);
				}
				catch(IllegalArgumentException e) {
					throw new PositionOutsideFieldException(x+1, y+1);
				}
			}
			else if(matcher.group(3) != null) {
				return field.findUnitById(matcher.group(3));
			}
		}
		return null;
	}
	
	public static Card.Instance getReferencedInstance(String cardReferenceString, Session session, Player player)
			throws NoSuchPlayerException, TooManyPlayersException, PlayerUnspecifiedException, PositionOutsideFieldException {
		Matcher matcher = instanceRefRegex.matcher(cardReferenceString);
		
		if(matcher.find()) {
			Player target = player;
			
			if(matcher.group(1) != null) {
				target = session.getPlayerByDisplayedTitle(matcher.group(1));
				
				if(target == null) {
					throw new NoSuchPlayerException(matcher.group(1));
				}
			}
			
			if(matcher.group(2) != null) {
				if(player == null) throw new PlayerUnspecifiedException();
				
				target = session.getOppositePlayer(player);
			}
			
			if(target == null) throw new PlayerUnspecifiedException();
			
			switch(matcher.group(3).toLowerCase()) {
				case "hand": case "h":
					if(target != player) return null;
					
					return getInstanceFrom(matcher.group(4), target.getHand());
					
				case "field": case "f":
					return getInstanceFrom(matcher.group(4), target.getField());
					
				default:
					break;
			}
		}
		
		return null;
	}
	
	public static class NoSuchPlayerException extends Exception {
		private static final long serialVersionUID = 7913465345087250932L;
		
		@Getter private String playerTitle;
		
		public NoSuchPlayerException(String playerTitle) {
			super("There is no player with the title " + playerTitle);
			
			this.playerTitle = playerTitle;
		}
	}
}
